package com.za.framework.filter;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 打印sql
 *
 * @author WangYan
 * @Description
 * @create 2020-09-30 15:11
 */

/**
 * 打印sql
 *
 * @Author WangYan
 * @Date 2022/10/20 16:29
 */
@Component
@Profile({"office", "dev", "test", "local"})
//@Profile({"pre"})
//@Value("${printSqlLog}")动态选择打印
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}
)
public class SqlLogPrintInterceptor implements Interceptor {

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static ThreadLocal<SimpleDateFormat> dateTimeFormatter = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    Logger log = LoggerFactory.getLogger(SqlLogPrintInterceptor.class);
    @Value("${project.printSqlLog:true}")
    private Boolean printSqlLog;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            /**
             * 查询有两个方法 第二个直接给sql
             * 哈哈 解决了网上无法打印分页sql (分页sql一模一样问题)
             * @Author WangYan
             * @Date 2022/11/19 14:31
             */
            BoundSql boundSql = invocation.getArgs().length != 6 ? mappedStatement.getBoundSql(invocation.getArgs()[1]) : (BoundSql) invocation.getArgs()[5];
            Object result = null;
            try {
                result = invocation.proceed();
                return result;
            } catch (Exception e) {
                printSqlLogTry(mappedStatement.getConfiguration(), boundSql, mappedStatement.getId(), result);
                throw e;
            } finally {
                if (printSqlLog != null && printSqlLog) {
                    printSqlLogTry(mappedStatement.getConfiguration(), boundSql, mappedStatement.getId(), result);
                }
            }
        } catch (Exception e) {
            //有异常，直接抛出，及时处理
            throw e;
        }
    }

    private void printSqlLogTry(Configuration configuration, BoundSql boundSql, String id, Object result) {
        try {
            String sql = this.getSql(configuration, boundSql);
            this.formatSqlLog(id, sql, result);
        } catch (Exception e) {
            log.error("打印sql日志异常", e);
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 获取完整的sql语句
     *
     * @param configuration
     * @param boundSql
     * @return
     */
    private String getSql(Configuration configuration, BoundSql boundSql) {
        // 输入sql字符串空判断
        String sql = boundSql.getSql();
        if (StringUtils.isEmpty(sql)) {
            return "";
        }
        return formatSql(sql, configuration, boundSql);
    }

    /**
     * 将占位符替换成参数值
     *
     * @param sql
     * @param configuration
     * @param boundSql
     * @return
     */
    private String formatSql(String sql, Configuration configuration, BoundSql boundSql) {

        //美化sql
        sql = beautifySql(sql);

        //填充占位符, 目前基本不用mybatis存储过程调用,故此处不做考虑
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

        List<String> parameters = new ArrayList<>();
        if (parameterMappings != null) {
            MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    //  参数值
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    //  获取参数名称
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        // 获取参数值
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        // 如果是单个值则直接赋值
                        value = parameterObject;
                    } else {
                        value = metaObject == null ? null : metaObject.getValue(propertyName);
                    }

                    if (value instanceof Number) {
                        parameters.add(String.valueOf(value));
                    } else {
                        StringBuilder builder = new StringBuilder();
                        builder.append("'");
                        if (value instanceof Date) {
                            builder.append(dateTimeFormatter.get().format((Date) value));
                        } else if (value instanceof String) {
                            builder.append(value);
                        }
                        builder.append("'");
                        parameters.add(builder.toString());
                    }
                }
            }
        }

        for (String value : parameters) {
            sql = sql.replaceFirst("\\?", value);
        }
        return sql;
    }

    /**
     * 格式化sql日志
     *
     * @param sqlId
     * @param sql
     * @return
     */
    private void formatSqlLog(String sqlId, String sql, Object result) {
        StringBuilder sqlLog = new StringBuilder("\n==> ").append(sql);
        if (result instanceof Collection) {
            int size = ((Collection) result).size();
            sqlLog.append("\n==> Total: " + size);
            if (size == 1) {
                Object data = ((Collection) result).iterator().next();
                if (data instanceof Number
                        || data instanceof String
                        || data instanceof BigDecimal) {
                    sqlLog.append("\n==> Result: " + data);
                }
            }
        }
        Logger log = LoggerFactory.getLogger(sqlId);
        log.info(sqlLog.toString());
        sqlLog = null;
    }

    public static String beautifySql(String sql) {
        sql = sql.replaceAll("[\\s\n ]+", " ");
        return sql;
    }
}
